package com.eblacorp.archive.util.ce.oracle;

import java.beans.*;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.*;
import java.util.*;

import org.apache.commons.lang.Validate;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.eblacorp.archive.annotations.*;
import com.eblacorp.archive.annotations.Property.Access;
import com.eblacorp.archive.domain.fn.Archivable;
import com.eblacorp.archive.exceptions.NotFnDocumentException;
import com.eblacorp.archive.exceptions.NullValueException;
import com.eblacorp.archive.util.FileUtil;
import com.eblacorp.archive.util.FnDateTimeFormatter;
import com.eblacorp.archive.util.MimeTypeUtils;
import com.eblacorp.archive.util.ce.ContentEngineUtil;
import com.eblacorp.archive.util.scanning.ArchiveEntityRepository;
import com.filenet.api.collection.ContentElementList;
import com.filenet.api.collection.IndependentObjectSet;
import com.filenet.api.collection.RepositoryRowSet;
import com.filenet.api.collection.VersionableSet;
import com.filenet.api.constants.AutoClassify;
import com.filenet.api.constants.CheckinType;
import com.filenet.api.constants.PropertyNames;
import com.filenet.api.constants.RefreshMode;
import com.filenet.api.constants.ReservationType;
import com.filenet.api.core.ContentTransfer;
import com.filenet.api.core.Document;
import com.filenet.api.core.Factory;
import com.filenet.api.core.ObjectStore;
import com.filenet.api.core.Versionable;
import com.filenet.api.property.FilterElement;
import com.filenet.api.property.PropertyFilter;
import com.filenet.api.query.RepositoryRow;
import com.filenet.api.query.SearchSQL;
import com.filenet.api.util.Id;

public class ContentEngineUtilImpl<T extends Archivable> implements
		ContentEngineUtil<T> {

	/**
	 * 
	 */
	private static final long serialVersionUID = 1274103911457193611L;

	final static Log log = LogFactory.getLog(ContentEngineUtilImpl.class);

	private Class<T> persistentClass;
	private String persistentClassName;
	private String classSymbolicName;
	private Map<String, Field> propertyRepository;

	private static final String COLUMN_LEFT_DELIMITER = "[";
	private static final String COLUMN_RIGHT_DELIMITER = "]";
	private static final String LIKE_OPERATOR = "%";
	private static final String LIKE = " like ";
	private static final String EQUAL = " = ";
	private static final String EQUAL_OR_GREATER = " >= ";
	private static final String EQUAL_OR_LESS = " <= ";
	private static final String AND = " and ";
	private static final String COLUMN_SEPARATOR = ",";
	private static final String SINGLE_QUOTE = "'";
	public static final int QUERY_TIME_LIMIT = 5; //Seconds

	private ArchiveEntityRepository archiveEntityRepository;

	@Override
	public SearchSQL getSeatchSQL(T t, Integer maxRecords, String[] dateProperties, Date[] dateFrom, Date[] dateTo) throws NotFnDocumentException,
			IntrospectionException, IllegalArgumentException,
			IllegalAccessException, InvocationTargetException {
		
		Field field = null;
		String propertySymbolicName = null;
		PropertyDescriptor propertyDescriptor = null;
		StringBuffer selectList = new StringBuffer();
		StringBuffer whereClause = new StringBuffer();

		SearchSQL searchSQL = new SearchSQL();
		searchSQL.setFromClauseInitialValue(classSymbolicName, null, true);
		
		Iterator<String> itr = propertyRepository.keySet().iterator();
		while (itr.hasNext()) {
			propertySymbolicName = itr.next();
			
			field = propertyRepository.get(propertySymbolicName);
			selectList.append(propertySymbolicName);
			if(itr.hasNext()){
				selectList.append(COLUMN_SEPARATOR);
			}
			propertyDescriptor = new PropertyDescriptor(field.getName(),
					t.getClass());
			if (propertyDescriptor.getReadMethod() != null
					&& !"class".equals(propertyDescriptor.getName())) {
				Object value = propertyDescriptor.getReadMethod().invoke(t);
				if (value != null) {
					if (propertyDescriptor.getPropertyType().equals(
							String.class)) {
						whereClause.append(COLUMN_LEFT_DELIMITER)
								.append(propertySymbolicName)
								.append(COLUMN_RIGHT_DELIMITER).append(LIKE)
								.append(SINGLE_QUOTE).append(LIKE_OPERATOR)
								.append(value.toString()).append(LIKE_OPERATOR)
								.append(SINGLE_QUOTE).append(AND);
					} else {
						if (propertyDescriptor.getPropertyType().equals(
								Date.class)) {
							Calendar cal = Calendar.getInstance();
							cal.setTime((Date) value);

							whereClause.append(COLUMN_LEFT_DELIMITER)
									.append(propertySymbolicName)
									.append(COLUMN_RIGHT_DELIMITER)
									.append(EQUAL_OR_GREATER)
									.append(FnDateTimeFormatter.format(cal.getTime())).append(AND);

							cal.add(Calendar.DATE, 1);
							whereClause.append(propertySymbolicName)
									.append(EQUAL_OR_LESS)
									.append(FnDateTimeFormatter.format(cal.getTime())).append(AND);
						} else {
							whereClause.append(COLUMN_LEFT_DELIMITER)
									.append(propertySymbolicName)
									.append(COLUMN_RIGHT_DELIMITER)
									.append(EQUAL).append(value.toString())
									.append(AND);
						}
					}
				}else{
					if (propertyDescriptor.getPropertyType().equals(
							Date.class)) {
						Calendar cal = Calendar.getInstance();
						if(dateProperties != null){
							for(int i = 0; i < dateProperties.length; i++){
								String dateParam = dateProperties[i];
								if(propertySymbolicName.equals(dateParam)){
									cal.setTime((Date) dateFrom[i]);

									whereClause.append(COLUMN_LEFT_DELIMITER)
											.append(propertySymbolicName)
											.append(COLUMN_RIGHT_DELIMITER)
											.append(EQUAL_OR_GREATER)
											.append(FnDateTimeFormatter.format(cal.getTime())).append(AND);

									cal.setTime((Date) dateTo[i]);
									cal.add(Calendar.DATE, 1);
									whereClause.append(propertySymbolicName)
											.append(EQUAL_OR_LESS)
											.append(FnDateTimeFormatter.format(cal.getTime())).append(AND);
								}
							}
						}
					}
				}
			}

		}

		whereClause.append("1 = 1");
		
		searchSQL.setSelectList(selectList.toString());
		searchSQL.setWhereClause(whereClause.toString());

		if (maxRecords != null && maxRecords.intValue() != 0) {
			searchSQL.setMaxRecords(maxRecords.intValue());
		}
		
		searchSQL.setTimeLimit(QUERY_TIME_LIMIT);

		return searchSQL;
	}

	@SuppressWarnings({ "unchecked" })
	@Override
	public List<T> getObjects(RepositoryRowSet repositoryRowSet)
			throws NotFnDocumentException, IllegalAccessException,
			InstantiationException, IntrospectionException,
			IllegalArgumentException, InvocationTargetException {
		T object = null;
		String propertySymbolicName = null;
		Field field = null;
		PropertyDescriptor propertyDescriptor = null;
		if (repositoryRowSet.isEmpty()) {
			return null;
		}

		List<T> objects = new ArrayList<T>();
		RepositoryRow repositoryRow = null;
		
		Iterator<RepositoryRow> rowsItr = repositoryRowSet.iterator();
		Iterator<String> propertiesItr = null;
		while (rowsItr.hasNext()) {
			repositoryRow = rowsItr.next();
			object = persistentClass.newInstance();
			propertiesItr = propertyRepository.keySet().iterator();
			while (propertiesItr.hasNext()) {
				propertySymbolicName = propertiesItr.next();
				field = propertyRepository.get(propertySymbolicName);
				propertyDescriptor = new PropertyDescriptor(field.getName(),
						persistentClass);
				com.filenet.api.property.Property fnProperty = repositoryRow
						.getProperties().get(propertySymbolicName);

				if (propertyDescriptor.getWriteMethod() != null) {
					if (propertyDescriptor.getPropertyType().equals(
							String.class)) {
						propertyDescriptor.getWriteMethod().invoke(object,
								fnProperty.getStringValue());
					} else {
						if (propertyDescriptor.getPropertyType().equals(
								Integer.class)) {
							propertyDescriptor.getWriteMethod().invoke(object,
									fnProperty.getInteger32Value());
						}else{
							if (propertyDescriptor.getPropertyType().equals(
									Date.class)) {
								propertyDescriptor.getWriteMethod().invoke(object,
										fnProperty.getDateTimeValue());
							}else{
								if (propertyDescriptor.getPropertyType().equals(
										Id.class)) {
									propertyDescriptor.getWriteMethod().invoke(object,
											fnProperty.getIdValue());
								}else{
									if (propertyDescriptor.getPropertyType().equals(
											Boolean.class)) {
										propertyDescriptor.getWriteMethod().invoke(object,
												fnProperty.getBooleanValue());
									}
								}
							}
						}
					}
				}
			}
			objects.add(object);
			object = persistentClass.newInstance();
		}
		return objects;

	}
	
	@SuppressWarnings({ "unchecked" })
	@Override
	public List<T> getObjects(IndependentObjectSet independentObjectSet) throws InstantiationException, IllegalAccessException, IntrospectionException, IllegalArgumentException, InvocationTargetException {
		T object = null;
		String propertySymbolicName = null;
		Field field = null;
		PropertyDescriptor propertyDescriptor = null;
		if (independentObjectSet.isEmpty()) {
			return null;
		}

		List<T> objects = new ArrayList<T>();
		Document document = null;
		Iterator<Document> objectsItr = independentObjectSet.iterator();
		Iterator<String> propertiesItr = null;
		while (objectsItr.hasNext()) {
			document = objectsItr.next();
			object = persistentClass.newInstance();
			propertiesItr = propertyRepository.keySet().iterator();
			while (propertiesItr.hasNext()) {
				propertySymbolicName = propertiesItr.next();
				field = propertyRepository.get(propertySymbolicName);
				propertyDescriptor = new PropertyDescriptor(field.getName(),
						persistentClass);
				com.filenet.api.property.Property fnProperty = document.
						getProperties().get(propertySymbolicName);

				if (propertyDescriptor.getWriteMethod() != null) {
					if (propertyDescriptor.getPropertyType().equals(
							String.class)) {
						propertyDescriptor.getWriteMethod().invoke(object,
								fnProperty.getStringValue());
					} else {
						if (propertyDescriptor.getPropertyType().equals(
								Integer.class)) {
							propertyDescriptor.getWriteMethod().invoke(object,
									fnProperty.getInteger32Value());
						}else{
							if (propertyDescriptor.getPropertyType().equals(
									Date.class)) {
								propertyDescriptor.getWriteMethod().invoke(object,
										fnProperty.getDateTimeValue());
							}else{
								if (propertyDescriptor.getPropertyType().equals(
										Id.class)) {
									propertyDescriptor.getWriteMethod().invoke(object,
											fnProperty.getIdValue());
								}else{
									if (propertyDescriptor.getPropertyType().equals(
											Boolean.class)) {
										propertyDescriptor.getWriteMethod().invoke(object,
												fnProperty.getBooleanValue());
									}
								}
							}
						}
					}
				}
			}
			objects.add(object);
			object.setDocument(document);
			object = persistentClass.newInstance();
		}
		return objects;

	}
	
	@Override
	public Document fetchDocumentById(ObjectStore objectStore, Id id, boolean withContent) {
		if(withContent){
			return Factory.Document.fetchInstance(objectStore, id, null);
		}
		PropertyFilter filter = new PropertyFilter();
		filter.addExcludeProperty(PropertyNames.THIS);
		return Factory.Document.fetchInstance(objectStore, id, filter);
	}
	
	public T getObject(Document document) throws InstantiationException, IllegalAccessException, IntrospectionException, IllegalArgumentException, InvocationTargetException{
		T object = null;
		String propertySymbolicName = null;
		Field field = null;
		PropertyDescriptor propertyDescriptor = null;
		
		if(document == null){
			//TODO : throws exception 11
			return null;
		}
		
		if(!document.get_ClassDescription().get_SymbolicName().equals(classSymbolicName)){
			//TODO : throws exception 22
			return null;
		}
		
		Iterator<String> propertiesItr = propertyRepository.keySet().iterator();
		object = persistentClass.newInstance();
		while (propertiesItr.hasNext()) {
			propertySymbolicName = propertiesItr.next();
			field = propertyRepository.get(propertySymbolicName);
			propertyDescriptor = new PropertyDescriptor(field.getName(),
					persistentClass);
			com.filenet.api.property.Property fnProperty = document.
					getProperties().get(propertySymbolicName);

			if (propertyDescriptor.getWriteMethod() != null) {
				if (propertyDescriptor.getPropertyType().equals(
						String.class)) {
					propertyDescriptor.getWriteMethod().invoke(object,
							fnProperty.getStringValue());
				} else {
					if (propertyDescriptor.getPropertyType().equals(
							Integer.class)) {
						propertyDescriptor.getWriteMethod().invoke(object,
								fnProperty.getInteger32Value());
					}else{
						if (propertyDescriptor.getPropertyType().equals(
								Date.class)) {
							propertyDescriptor.getWriteMethod().invoke(object,
									fnProperty.getDateTimeValue());
						}else{
							if (propertyDescriptor.getPropertyType().equals(
									Id.class)) {
								propertyDescriptor.getWriteMethod().invoke(object,
										fnProperty.getIdValue());
							}else{
								if (propertyDescriptor.getPropertyType().equals(
										Boolean.class)) {
									propertyDescriptor.getWriteMethod().invoke(object,
											fnProperty.getBooleanValue());
								}
							}
						}
					}
				}
			}
		}
		
		object.setDocument(document);
		return object;
	}
	
	@Override
	public Document createDocument(ObjectStore objectStore,T t) throws IntrospectionException, IllegalArgumentException, IllegalAccessException, InvocationTargetException, NullValueException {
		
		String propertySymbolicName = null;
		Field field = null;
		PropertyDescriptor propertyDescriptor = null;
		
		Document document = Factory.Document.createInstance(objectStore, classSymbolicName);
		Iterator<String> propertiesItr = propertyRepository.keySet().iterator();
		com.filenet.api.property.Properties fnProperty = document.getProperties();
		
		while (propertiesItr.hasNext()) {
			propertySymbolicName = propertiesItr.next();
			field = propertyRepository.get(propertySymbolicName);
			Property property  = field.getAnnotation(Property.class);
			if(property.access() == Access.READ_WRITE){
				propertyDescriptor = new PropertyDescriptor(field.getName(),
						persistentClass);			
	
				if (propertyDescriptor.getReadMethod() != null) {
					Object valueObject = propertyDescriptor.getReadMethod().invoke(t);
					if(property.required() && valueObject == null){
						throw new NullValueException(persistentClassName, field.getName());
					}
				    fnProperty.putObjectValue(propertySymbolicName, propertyDescriptor.getReadMethod().invoke(t));
				}
			}
		}
		return document;
	}
	
	@SuppressWarnings("unchecked")
	@Override
	public Document addContent(Document document, File file) throws IOException {
		
		Validate.notNull(document);
		Validate.notNull(file);
		
		String mimeType = null;
		
		
		byte[] bytes = FileUtil.getBytes(file);
		
		mimeType  =MimeTypeUtils.detectMimeType(file);;
		
		document.set_MimeType(mimeType);
		
		ContentTransfer contentTransfer = Factory.ContentTransfer.createInstance();
		contentTransfer.setCaptureSource(new ByteArrayInputStream(bytes));
		ContentElementList contentElementList = Factory.ContentElement.createList();
		contentElementList.add(contentTransfer);
		
		document.set_ContentElements(contentElementList);
		return document;
	}
	
	@Override
	public Document checkinDocument(Document document,CheckinType checkinType, RefreshMode refreshMode) {
		Validate.notNull(document);
		Validate.notNull(checkinType);
		Validate.notNull(refreshMode);
//		if(document.getProperties().isPropertyPresent(PropertyNames.RESERVATION)){
//			document = (Document) document.get_Reservation();
//		}
		document.checkin(AutoClassify.DO_NOT_AUTO_CLASSIFY, checkinType);
		document.save(refreshMode);	
		document.refresh();
		return document;
	}
	
	@Override
	public Document updateDocument(ObjectStore objectStore, T t) throws IntrospectionException, IllegalArgumentException, IllegalAccessException, InvocationTargetException, NullValueException {
		String propertySymbolicName = null;
		Field field = null;
		PropertyDescriptor propertyDescriptor = null;
		
		Document document = t.getDocument();
		Iterator<String> propertiesItr = propertyRepository.keySet().iterator();
		com.filenet.api.property.Properties fnProperty = document.getProperties();
		
		while (propertiesItr.hasNext()) {
			propertySymbolicName = propertiesItr.next();
			field = propertyRepository.get(propertySymbolicName);
			Property property  = field.getAnnotation(Property.class);
			if(property.access() == Access.READ_WRITE){
				propertyDescriptor = new PropertyDescriptor(field.getName(),
						persistentClass);			
	
				if (propertyDescriptor.getReadMethod() != null) {
					Object valueObject = propertyDescriptor.getReadMethod().invoke(t);
					if(property.required() && valueObject == null){
						throw new NullValueException(persistentClassName, field.getName());
					}
				    fnProperty.putObjectValue(propertySymbolicName, propertyDescriptor.getReadMethod().invoke(t));
				}	
			}
		}
		return document;
	}
	
	@Override
	public Document checkoutDocument(Document document) {
		Validate.notNull(document);
		if(!document.get_IsReserved().booleanValue()){
			document = (Document) document.get_CurrentVersion();
			document.checkout(ReservationType.COLLABORATIVE, null, null, document.getProperties());
			document.save(RefreshMode.NO_REFRESH);
         }
		return (Document)document.get_Reservation();	
	}
	
	@Override
	public void saveDocument(Document document, RefreshMode refreshMode) {
		Validate.notNull(document);
		Validate.notNull(refreshMode);
		document.save(refreshMode);
		
	}
	
	@SuppressWarnings("unchecked")
	@Override
	public void delete(Document doument, boolean allVersion) {
		doument.refresh();
		if(allVersion){
			VersionableSet versionableSet = doument.get_Versions();
			Iterator<Versionable> itr = versionableSet.iterator();
			while(itr.hasNext()){
				Document version = (Document) itr.next();
				version.refresh();
				version.delete();
				version.save(RefreshMode.NO_REFRESH);
			}
		} else{
			doument.delete();
			doument.save(RefreshMode.NO_REFRESH);
		}
	}
	
	@Override
	public Document fillFnDocument(T t, ObjectStore objectStore) throws IntrospectionException, IllegalArgumentException, IllegalAccessException, InvocationTargetException, NullValueException {
		String propertySymbolicName = null;
		Field field = null;
		PropertyDescriptor propertyDescriptor = null;
		Document document = null;
		if(t.getDocument() != null){
			document = t.getDocument();
		}else{
			document = Factory.Document.fetchInstance(objectStore, t.getDocumentId(), null);
		}
		Iterator<String> propertiesItr = propertyRepository.keySet().iterator();
		com.filenet.api.property.Properties fnProperty = document.getProperties();
		
		while (propertiesItr.hasNext()) {
			propertySymbolicName = propertiesItr.next();
			field = propertyRepository.get(propertySymbolicName);
			Property property  = field.getAnnotation(Property.class);
			if(property.access() == Access.READ_WRITE){
				propertyDescriptor = new PropertyDescriptor(field.getName(),
						persistentClass);			
	
				if (propertyDescriptor.getReadMethod() != null) {
					Object valueObject = propertyDescriptor.getReadMethod().invoke(t);
					if(property.required() && valueObject == null){
						throw new NullValueException(persistentClassName, field.getName());
					}
				    fnProperty.putObjectValue(propertySymbolicName, propertyDescriptor.getReadMethod().invoke(t));
				}
			}
		}
		return document;
	}
	
	@Override
	public byte[] download(Document document, ObjectStore objectStore) throws IOException {
		Validate.notNull(objectStore);
		Validate.notNull(document);
		byte[] buffer = new byte[1024 * 8];
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		if(!document.getProperties().isPropertyPresent("ContentElements")){
			PropertyFilter filter = new PropertyFilter();
			filter.addIncludeProperty(new FilterElement(null, null, null, PropertyNames.CONTENT_ELEMENTS, null));
			document = Factory.Document.fetchInstance(objectStore, document.get_Id(), filter);
		}
		InputStream is = document.accessContentStream(0);
		int length = is.read(buffer);
		while(length != -1){
			baos.write(buffer);
			length = is.read(buffer);
		}
		return baos.toByteArray();
	}
	
	public ArchiveEntityRepository getArchiveEntityRepository() {
		return archiveEntityRepository;
	}

	public void setArchiveEntityRepository(
			ArchiveEntityRepository archiveEntityRepository) {
		this.archiveEntityRepository = archiveEntityRepository;
	}

	public void setPersistentClass(Class<T> persistentClass)
			throws NotFnDocumentException {
		this.persistentClass = persistentClass;
		if (persistentClass.isAnnotationPresent(DocumentClass.class)) {
			classSymbolicName = persistentClass.getAnnotation(DocumentClass.class).symbolicName();
			propertyRepository = archiveEntityRepository
					.getPropertyRepository().get(classSymbolicName);
		} else {
			throw new NotFnDocumentException(persistentClassName);
		}
		persistentClassName = persistentClass.getName();
	}
}
